package cn.tpshion.vm.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.tpshion.vm.base.config.NodeConfig;
import cn.tpshion.vm.common.constant.MsgType;
import cn.tpshion.vm.common.constant.NodeStatus;
import cn.tpshion.vm.common.constant.NodeType;
import cn.tpshion.vm.common.exception.BizException;
import cn.tpshion.vm.common.model.RequestContext;
import cn.tpshion.vm.common.utils.AssertUtil;
import cn.tpshion.vm.common.utils.DateUtil;
import cn.tpshion.vm.domain.dto.task.*;
import cn.tpshion.vm.domain.entity.Task;
import cn.tpshion.vm.domain.entity.TaskHis;
import cn.tpshion.vm.domain.entity.TaskNamespace;
import cn.tpshion.vm.domain.entity.TaskTemplate;
import cn.tpshion.vm.domain.entity.table.TaskHisTableDef;
import cn.tpshion.vm.domain.entity.table.TaskNamespaceTableDef;
import cn.tpshion.vm.domain.entity.table.TaskTableDef;
import cn.tpshion.vm.domain.support.*;
import cn.tpshion.vm.domain.vo.TaskInfoVO;
import cn.tpshion.vm.domain.vo.TaskVO;
import cn.tpshion.vm.handler.GitBranchHandle;
import cn.tpshion.vm.handler.SSEHandle;
import cn.tpshion.vm.handler.task.support.NodeFactory;
import cn.tpshion.vm.handler.task.support.NodeHandler;
import cn.tpshion.vm.mapper.TaskHisMapper;
import cn.tpshion.vm.mapper.TaskMapper;
import cn.tpshion.vm.mapper.TaskNamespaceMapper;
import cn.tpshion.vm.mapper.TaskTemplateMapper;
import cn.tpshion.vm.service.TaskService;
import com.alibaba.fastjson2.JSON;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.FutureTask;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

@Slf4j
@Service
public class TaskServiceImpl implements TaskService {

    private volatile Map<String, NodeContext> contextMap = new ConcurrentHashMap<>();

    private volatile Map<String, FutureTask> taskMap = new ConcurrentHashMap<>();

    private final ReentrantLock lock = new ReentrantLock();

    @Resource
    private TaskNamespaceMapper taskNamespaceMapper;

    @Resource
    private TaskMapper taskMapper;

    @Resource
    private TaskHisMapper taskHisMapper;

    @Resource
    private NodeFactory nodeFactory;

    @Resource
    private SSEHandle sseHandle;

    @Resource
    private NodeConfig config;

    @Resource
    private ThreadPoolTaskExecutor taskExecutor;

    @Resource
    private GitBranchHandle gitBranchHandle;

    @Resource
    private TaskTemplateMapper taskTemplateMapper;

    @Override
    public List<TaskNamespace> getTaskNamespaceList() {
        return taskNamespaceMapper.selectAll();
    }

    @Override
    public TaskNamespace addNamespace(AddNamespaceDTO dto) {
        // 检查是否已存在
        QueryWrapper queryWrapper = QueryWrapper.create().where(TaskNamespaceTableDef.TASK_NAMESPACE.NAME.eq(dto.getName()));
        TaskNamespace taskNamespace = taskNamespaceMapper.selectOneByQuery(queryWrapper);
        AssertUtil.isTrue(Objects.nonNull(taskNamespace), "命名空间已存在，不能重复添加");
        TaskNamespace namespace = new TaskNamespace(IdUtil.fastUUID(), dto.getName());
        taskNamespaceMapper.insert(namespace);
        return namespace;
    }

    @Override
    public void deleteNamespace(String id) {
        QueryWrapper taskQuery = QueryWrapper.create().where(TaskTableDef.TASK.NAMESPACE.eq(id));
        long count = taskMapper.selectCountByQuery(taskQuery);
        AssertUtil.isTrue(count > 0, "该命名空间下存在流水线任务，不能删除");

        QueryWrapper queryWrapper = QueryWrapper.create().where(TaskNamespaceTableDef.TASK_NAMESPACE.ID.eq(id));
        taskNamespaceMapper.deleteByQuery(queryWrapper);
    }

    @Override
    public Page<TaskVO> taskList(QueryTaskDTO dto) {
        Page<TaskVO> resultPage = new Page<>();
        QueryWrapper queryWrapper = new QueryWrapper();
        if(StringUtils.isNotBlank(dto.getNamespace())){
            queryWrapper.where(TaskTableDef.TASK.NAMESPACE.eq(dto.getNamespace()));
        }
        Page<Task> page = taskMapper.paginate(dto.getPageNum(), dto.getPageSize(), queryWrapper);
        List<TaskVO> taskList = Optional.ofNullable(page.getRecords())
                .map(list -> list.stream().map(task -> {
                    TaskVO taskVO = new TaskVO();
                    taskVO.setId(task.getId());
                    taskVO.setName(task.getName());
                    taskVO.setNamespace(task.getNamespace());
                    taskVO.setLastTime(task.getCreateTime());

                    QueryWrapper hisQuery = new QueryWrapper();
                    hisQuery.where(TaskHisTableDef.TASK_HIS.TASK_ID.eq(task.getId()))
                            .orderBy(TaskHisTableDef.TASK_HIS.ID.desc())
                            .limit(1);
                    TaskHis taskHis = taskHisMapper.selectOneByQuery(hisQuery);
                    if(Objects.nonNull(taskHis)){
                        taskVO.setLastTime(taskHis.getCreateTime());
                        taskVO.setLastStatus(taskHis.getStatus());
                        taskVO.setLastMsg(taskHis.getGitMsg());
                        taskVO.setLastBranch(taskHis.getGitBranch());
                        taskVO.setLastOperator(taskHis.getOptUser());
                    }
                    return taskVO;
                })).orElseGet(null).collect(Collectors.toList());
        resultPage.setRecords(taskList);
        resultPage.setTotalRow(page.getTotalRow());
        return resultPage;
    }

    @Override
    public void addTask(AddTaskDTO dto) {
        QueryWrapper queryWrapper = QueryWrapper.create().where(TaskTableDef.TASK.NAME.eq(dto.getName())
                .and(TaskTableDef.TASK.NAMESPACE.eq(dto.getNamespace())));
        Task task = taskMapper.selectOneByQuery(queryWrapper);
        AssertUtil.isNotEmpty(task, "流水线名称已存在");

        if(dto.isEdit()){
            AssertUtil.isBlank(dto.getId(), "流水线id不能为空");
            Task editTask = new Task();
            editTask.setId(dto.getId());
            editTask.setName(dto.getName());
            editTask.setNamespace(dto.getNamespace());
            taskMapper.update(editTask, true);
        }else{
            Task taskAdd = new Task();
            taskAdd.setId(IdUtil.fastUUID());
            taskAdd.setName(dto.getName());
            taskAdd.setNamespace(dto.getNamespace());
            taskAdd.setCreateTime(DateUtil.now());
            taskMapper.insertSelective(taskAdd);
        }
    }

    @Override
    public void deleteTask(DeleteTaskDTO dto) {
        dto.getList().forEach(i -> {
            try {
                File file = new File(config.getWorkspace() + File.separator + i);
                if(file.isDirectory() && file.exists()){
                    // 删除工作空间
                    FileUtils.deleteDirectory(file);
                }
                taskMapper.deleteById(i);
            }catch (Exception e){
                log.error("deleteTask taskId:{} error:{}", i, e.getMessage(), e);
            }
        });
    }

    @Override
    public TaskInfoVO getTaskInfo(String id, Boolean edit) {
        Task task = taskMapper.selectOneById(id);
        AssertUtil.isEmpty(task, "流水线不存在");

        TaskInfoVO result = new TaskInfoVO();
        BeanUtils.copyProperties(task, result);

        NodeContext context = contextMap.get(id);
        if(Objects.nonNull(context) && !ObjectUtils.isEmpty(context.getDataList()) && !edit){
            result.setTaskDataList(context.getDataList());
            result.setRunningFlag(true);
        }else if(StringUtils.isNotBlank(task.getTaskData())){
            result.setTaskDataList(JSON.parseArray(task.getTaskData(), TaskData.class));
        }

        TaskNamespace taskNamespace = taskNamespaceMapper.selectOneById(task.getNamespace());
        if(Objects.nonNull(taskNamespace)){
            result.setNamespaceRel(taskNamespace.getName());
        }
        return result;
    }

    @Override
    public void saveFlowData(TaskInfoDTO dto) {
        QueryWrapper queryWrapper = QueryWrapper.create().where(TaskTableDef.TASK.ID.eq(dto.getId()));
        Task task = taskMapper.selectOneByQuery(queryWrapper);
        AssertUtil.isEmpty(task, "数据异常，流水线不存在");

        Task updateTask = new Task();
        updateTask.setId(dto.getId());
        updateTask.setFlowData(dto.getFlowData());
        if(!ObjectUtils.isEmpty(dto.getTaskDataList())){
            updateTask.setTaskData(JSON.toJSONString(dto.getTaskDataList()));
        }
        taskMapper.update(updateTask, true);
    }

    @Override
    public void runTask(String taskId) {
        String userName = RequestContext.getUserName();
        AssertUtil.isFalse(lock.tryLock(), "任务处理中，请等待执行完成!");
        try {
            QueryWrapper queryWrapper = QueryWrapper.create().where(TaskTableDef.TASK.ID.eq(taskId));
            Task task = taskMapper.selectOneByQuery(queryWrapper);
            AssertUtil.isEmpty(task, "数据异常，流水线不存在");

            AssertUtil.isBlank(task.getTaskData(), "流程数据错误，请检查配置");
            List<TaskData> dataList = JSON.parseArray(task.getTaskData(), TaskData.class);
            AssertUtil.isEmpty(dataList, "流程数据错误，请检查配置");

            AssertUtil.isTrue(taskMap.containsKey(taskId), "任务处理中，请等待执行完成!");

            Long batchId = task.getBatchId() + 1;

            Task updateTask = new Task();
            updateTask.setId(task.getId());
            updateTask.setBatchId(batchId);
            int rs = taskMapper.update(updateTask, true);
            AssertUtil.isTrue(rs <= 0, "更新任务失败");

            TaskHis taskHis = new TaskHis();
            taskHis.setCreateTime(DateUtil.now());
            taskHis.setStatus(NodeStatus.RUN.getStatus());
            taskHis.setBatchId(batchId);
            taskHis.setTaskId(taskId);
            taskHis.setOptUser(userName);
            taskHis.setLogFile(config.getWorkspace() + File.separator + taskId + File.separator + "log" + File.separator + batchId + ".log");
            taskHisMapper.insert(taskHis);

            FutureTask futureTask = new FutureTask(() -> {
                final Task tempTask = task;
                final List<TaskData> dataListTemp = dataList;

                NodeContext context = new NodeContext();
                contextMap.put(tempTask.getId(), context);
                context.setTaskId(tempTask.getId());
                context.setBatchId(batchId);
                context.setDataList(dataListTemp);

                try {
                    beforeFlow(context);
                    for (TaskData taskData : dataListTemp) {
                        if(context.isStopFlag()){
                            break;
                        }
                        if(Thread.currentThread().isInterrupted()){
                            context.setStopFlag(true);
                            break;
                        }
                        context.setTaskData(taskData);
                        context.setType(taskData.getType());
                        NodeHandler handler = nodeFactory.getHandler(context);
                        handler.handle(context);
                    }
                    afterFlow(context);
                }catch (Exception e){
                    log.info("runTask error:{}", e.getMessage(), e);
                    errorHandle(context, e);
                }finally {
                    contextMap.remove(tempTask.getId());
                    taskMap.remove(tempTask.getId());
                }
                return true;
            });
            taskMap.put(task.getId(), futureTask);
            taskExecutor.submit(futureTask);
        }finally {
            lock.unlock();
        }
    }

    private void beforeFlow(NodeContext nodeContext){
        FlowStatusData status = new FlowStatusData();
        status.setTaskId(nodeContext.getTaskId());
        status.setStatus(true);

        MsgData<FlowStatusData> data = new MsgData<>(MsgType.FLOW);
        data.setData(status);
        sseHandle.sendMessage(nodeContext, data);
    }

    private void afterFlow(NodeContext nodeContext){
        FlowStatusData status = new FlowStatusData();
        status.setTaskId(nodeContext.getTaskId());
        status.setStatus(false);

        MsgData<FlowStatusData> data = new MsgData<>(MsgType.FLOW);
        data.setData(status);
        sseHandle.sendMessage(nodeContext, data);

        TaskHis taskHis = new TaskHis();
        taskHis.setStatus(NodeStatus.SUCCESS.getStatus());
        updateTaskHis(taskHis, nodeContext);
    }

    private void updateTaskHis(TaskHis taskHis, NodeContext context){
        QueryWrapper queryWrapper = QueryWrapper.create().where(
                TaskHisTableDef.TASK_HIS.BATCH_ID.eq(context.getBatchId())
                        .and(TaskHisTableDef.TASK_HIS.TASK_ID.eq(context.getTaskId()))
        );
        taskHisMapper.updateByQuery(taskHis, true, queryWrapper);
    }

    private void errorHandle(NodeContext nodeContext, Exception e){
        TaskData taskData = nodeContext.getTaskData();
        taskData.setStatus(NodeStatus.ERROR.getStatus());

        handleNodeStatus(nodeContext, taskData);

        sseHandle.sendMessage(nodeContext, MsgData.of(MsgType.ERROR, taskData.getTitle() + " 执行失败：" + e.getMessage()));
        StringBuilder builder = new StringBuilder();
        for (StackTraceElement traceElement : e.getStackTrace()) {
            builder.append(traceElement.toString());
        }
        sseHandle.sendMessage(nodeContext, MsgData.of(MsgType.ERROR, builder.toString()));

        TaskHis taskHis = new TaskHis();
        taskHis.setStatus(NodeStatus.ERROR.getStatus());
        updateTaskHis(taskHis, nodeContext);
    }

    private void handleNodeStatus(NodeContext nodeContext, TaskData taskData){
        NodeStatusData status = new NodeStatusData();
        status.setId(taskData.getId());
        status.setStatus(taskData.getStatus());

        MsgData<NodeStatusData> data = new MsgData<>(MsgType.NODE);
        data.setData(status);
        sseHandle.sendMessage(nodeContext, data);
    }

    @Override
    public void stopTask(String taskId) {
        AssertUtil.isBlank(taskId, "流程id不能为空");
        dockerStop(taskId);
        FutureTask futureTask = taskMap.get(taskId);
        if(Objects.nonNull(futureTask)){
            futureTask.cancel(true);
        }
    }

    private void dockerStop(String taskId){
        NodeContext context = contextMap.get(taskId);
        if(Objects.isNull(context)){
            return;
        }
        NodeHandler handler = nodeFactory.getHandler(context);
        try {
            handler.stop(context);
        } catch (Exception e) {
            log.info("stopTask error:{}", e.getMessage(), e);
        }
    }

    @Override
    public Page<TaskHis> hisList(QueryHisDTO dto) {
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.where(TaskHisTableDef.TASK_HIS.TASK_ID.eq(dto.getTaskId()));
        queryWrapper.orderBy(TaskHisTableDef.TASK_HIS.ID.desc());
        return taskHisMapper.paginate(dto.getPageNum(), dto.getPageSize(), queryWrapper);
    }

    @Override
    public List<String> hisLog(String taskId, Long batchId) {
        QueryWrapper queryWrapper = QueryWrapper.create().where(
                TaskHisTableDef.TASK_HIS.TASK_ID.eq(taskId)
                        .and(TaskHisTableDef.TASK_HIS.BATCH_ID.eq(batchId))
        ).limit(1);

        TaskHis taskHis = taskHisMapper.selectOneByQuery(queryWrapper);
        AssertUtil.isTrue(Objects.isNull(taskHis) || StringUtils.isBlank(taskHis.getLogFile()), "日志数据不存在");

        File file = new File(taskHis.getLogFile());
        log.info("historyLog file:{}", file.getPath());
        AssertUtil.isTrue(!file.exists() || file.isDirectory(), "读取日志失败");
        try {
            return FileUtils.readLines(file, StandardCharsets.UTF_8);
        } catch (IOException e) {
            log.error("historyLog error:{}", e.getMessage(), e);
            throw new BizException("读取日志失败");
        }
    }

    @Override
    public List<String> branchList(String taskId) {
        Task task = taskMapper.selectOneById(taskId);
        AssertUtil.isEmpty(task, "流水线数据异常");

        AssertUtil.isBlank(task.getTaskData(), "流程数据错误，请检查配置");
        List<TaskData> dataList = JSON.parseArray(task.getTaskData(), TaskData.class);
        AssertUtil.isEmpty(dataList, "流程数据错误，请检查配置");

        Optional<TaskData> optional = dataList.stream().filter(i -> NodeType.GIT.getType().equals(i.getType())).findFirst();
        AssertUtil.isFalse(optional.isPresent(), "git节点不存在，获取不到分支信息");

        TaskData taskData = optional.get();
        GitData data = JSON.parseObject(JSON.toJSONString(taskData.getData()), GitData.class);
        try {
            return gitBranchHandle.getBranchList(data, taskId);
        } catch (Exception e) {
            log.error("branchList error:{}",e.getMessage(), e);
            throw new BizException("查询分支失败");
        }
    }

    @Override
    public void updateTaskData(TaskInfoDTO dto) {
        Task updateTask = new Task();
        updateTask.setId(dto.getId());
        updateTask.setTaskData(JSON.toJSONString(dto.getTaskDataList()));
        taskMapper.update(updateTask, true);
    }

    @Override
    public void saveTemplate(TaskTemplateDTO dto) {
        TaskTemplate template = new TaskTemplate();
        template.setName(dto.getName());
        template.setFlowData(dto.getFlowData());
        template.setCreateTime(DateUtil.now());
        taskTemplateMapper.insert(template);
    }

    @Override
    public void deleteTemplate(Long id) {
        taskTemplateMapper.deleteById(id);
    }

    @Override
    public List<TaskTemplate> templateList() {
        return taskTemplateMapper.selectAll();
    }
}
